/* Copyright (c) 2011 Jordan Gray, MIT; see COPYRIGHT */


#ifndef LINT_REP_CC_
#define LINT_REP_CC_

#include "lint_rep.h"

#include <iostream>
#include <sstream>
#include <assert.h>

namespace ch11ex05 {

LINT::LINTRep::~LINTRep() {
 //std::cout << "destroying LINTRep w/ " << digits_ << std::endl;
}

void LINT::LINTRep::UnsignedAdd(const LINTRep& rep) {
  char carry = 0;
  ListUChar::reverse_iterator dst=digits_.rbegin();
  ListUChar::const_reverse_iterator src=rep.digits_.rbegin();
  while(dst != digits_.rend()) {
    int op;
    if(src==rep.digits_.rend())
      op = 0;
    else
      op = *src++;
    *dst += op + carry;
    if (*dst > 9) {
      *dst -= 10;
      carry = 1;
    }
    else
      carry = 0;
    ++dst;
  }
  if(carry) digits_.push_front(carry);
}

// assumes this > rep, aka dst > src
// above assumption MUST be true to function properly
void LINT::LINTRep::UnsignedSubtract(const LINTRep& rep) {
  char carry = 0;
  ListUChar::reverse_iterator dst=digits_.rbegin();
  ListUChar::const_reverse_iterator src=rep.digits_.rbegin();
  while(dst != digits_.rend()) {
    int op;
    if(src == rep.digits_.rend())
      op = carry;
    else
      op = carry + *src++;
    if(*dst < op) {
      *dst += 10-op;
      carry = 1;
    }
    else {
      *dst += -op;
      carry = 0;
    }
    ++dst;
  }
  // trim leading 0's and prevent -0
  for(ListUChar::iterator it=digits_.begin(); it != digits_.end(); ++it) {
    if(*it == 0) {
      if(digits_.size() == 1) {
        negative_ = false; // "0" is stored as positive
        break; // nothing else to prune
      }
      else {
        digits_.erase(it--); // after erasing, reset pointer location
      }
    }
    else {
      break; // remove 0's until non-zero
    }
  }
}
bool LINT::LINTRep::IsEqualMagnitude(const LINTRep& repA, const LINTRep& repB) {
  if(repA.digits_.size() != repB.digits_.size()) return false;
  ListUChar::const_reverse_iterator it = repA.digits_.rbegin();
  ListUChar::const_reverse_iterator it_op = repB.digits_.rbegin();
  while(true) {
    if(*it++ != *it_op++) return false;
    if(it==repA.digits_.rend() && it_op==repB.digits_.rend()) return true;
  }
}

bool LINT::LINTRep::IsEqualSign(const LINTRep& repA, const LINTRep& repB) {
  return (repA.negative_ == repB.negative_);
}

bool LINT::LINTRep::IsEqual(const LINTRep& repA, const LINTRep& repB) {
  return IsEqualMagnitude(repA, repB) && IsEqualSign(repA, repB);
}

bool LINT::LINTRep::IsGreaterMagnitude(const LINTRep& rep_big, const LINTRep& rep_small) {
  const ListUChar& big = rep_big.digits_;
  const ListUChar& small = rep_small.digits_;
  if(big.size() > small.size()) return true;
  if(big.size() < small.size()) return false;
  // have not returned, so big.size()==small.size()
  ListUChar::const_iterator it_big = big.begin();
  ListUChar::const_iterator it_small = small.begin();
  while(true) {
    if(*it_big > *it_small) return true;
    if(*it_big < *it_small) return false;
    if(++it_big == big.end()) return false;
    ++it_small;
  }
}

void LINT::LINTRep::UnsignedMultiply(const LINTRep& rep) {
  for( LINTRep   one = LINTRep(ListUChar(1,1),false),
                unit = LINTRep(*this),
               index = LINTRep(one);
      !IsEqual(index,rep);
      index.UnsignedAdd(one)) {
    UnsignedAdd(unit);
  }
}

void LINT::LINTRep::UnsignedDivide(const LINTRep& rep) {
  LINTRep result = LINTRep(ListUChar(1,0), false);
  LINTRep one = LINTRep(ListUChar(1,1), false);
  while(!IsGreaterMagnitude(rep,*this)) {
    UnsignedSubtract(rep);
    result.UnsignedAdd(one);
  }
  std::swap(digits_,result.digits_);
}

void LINT::LINTRep::SignedMultiply(const LINTRep& rep) {
  UnsignedMultiply(rep);
  if(negative_ == rep.negative_ || IsZero(*this)) negative_ = false;
  else negative_ = true;
}

void LINT::LINTRep::SignedDivide(const LINTRep& rep) {
  UnsignedDivide(rep);
  if(negative_ == rep.negative_ || IsZero(*this)) negative_ = false;
  else negative_ = true;
}

std::string LINT::LINTRep::ToString(void) {
  std::stringstream ss;
  if(negative_) ss << "-";
  for(ListUChar::iterator it = digits_.begin(); it != digits_.end(); ++it) {
    ss << static_cast<int>(*it);
  }
  return ss.str();
  //return ( static_cast<std::ostringstream*>( &(std::ostringstream() << integer_) ) )->str();
}

void LINT::LINTRep::SignedAccumulate(const LINTRep& source, bool source_negate) {
  bool source_negative = source.negative_ ^ source_negate;
  if(negative_ == source_negative) {
    UnsignedAdd(source);
  }
  else { // need to subtract, which requires identifying the larger magnitude element
    if(IsGreaterMagnitude(source,*this)) {
      LINTRep new_source(*this);
      digits_ = source.digits_;
      negative_ = source_negative;
      UnsignedSubtract(new_source);
    }
    else { // equal, or dest is larger than source
      UnsignedSubtract(source);
    }
  }
}

bool LINT::LINTRep::IsZero(const LINTRep& rep) {
  if(rep.digits_ == std::list<unsigned char>(1,0)) return true;
  return false;
}

} // namespace ch11ex05

#endif /* LINT_REP_CC_ */
